/********************************************************************
	created:	2012/02/04
	filename: 	Ray.cpp
	author:		Mwolf
	
	purpose:	
*********************************************************************/

#include "Ray.h"
#include <cfloat>

#define EPSILON 0.00001f
#define FLOAT_EQUALS_ZERO(num) (fabs(num) <= EPSILON)
#define FLOAT_EQUAL(a, b) (fabs(a-b) <= EPSILON)

Ray Ray_ByDefinition( const Vector3& begin, const Vector3& end )
{
	Ray ray;
	ray.origin =	begin;
	ray.dir =		(end - begin).NormalizedVector();
	ray.dist =		sqrt( (end - begin).SqrdLen() );

	return ray;
}

float Ray_IntersectTriangle( const Ray& ray, const Vector3& v1, const Vector3& v2, const Vector3& v3, bool cull /*= true*/, Vector3* vHit /*= 0*/ )
{
	Vector3 e1,e2,p,s,q;
	float u,v,tmp;
	e1 = v2 - v1;
	e2 = v3 - v1;
	p = Vector3::Cross(ray.dir, e2);

	// decide face normal
	Vector3 normal = Vector3::Cross(e1, e2);
	normal.Normalize();

	// cull back face
	if (cull && Vector3::Dot(ray.dir, normal)>0)
		return -1.f;

	tmp = Vector3::Dot(p, e1);
	if (FLOAT_EQUALS_ZERO(tmp))
	{
		return -1.f;
	}
	tmp = 1.0f/tmp;
	s = ray.origin - v1;
	u = Vector3::Dot(s, p) * tmp;
	if (u<0.0f || u>1.0f)
	{
		return -1.f;
	}
	q = Vector3::Cross(s,e1);
	v = tmp * Vector3::Dot(ray.dir, q);
	if (v<0.0f || v>1.0f)
	{
		return -1.f;
	}
	if (u+v>1.0f)
		return -1.f; 

	// distance of ray
	float tmpD = tmp * Vector3::Dot(e2, q);

	if (vHit) *vHit = ray.origin + ray.dir * tmpD;
	return tmpD;
}

float Ray_IntersectBox( const Ray& ray, const Vector3& vMin, const Vector3& vMax, Vector3* vHit /*= 0*/, int* faceId /*= 0*/ )
{

#define VECTOR_LESS(a, b) (a.x<b.x && a.y<b.y && a.z<b.z)
#define VECTOR_GREATER(a, b) (a.x>b.x && a.y>b.y && a.z>b.z)

	// Point inside box
	if (VECTOR_LESS(ray.origin, vMax) && VECTOR_GREATER(ray.origin, vMin))
	{
		if (vHit) *vHit = ray.origin;
		return 0.0f;
	}

	float d_min = FLT_MAX;
	float d;
	bool hit = false;
	Vector3 point, point_nearest;
	int hit_face = -1;

	// x+
	if (Vector3::Dot(ray.dir, Vector3(1.0f, 0.0f, 0.0f))<0)
	{
		d = Ray_IntersectTriangle(ray,
			Vector3(vMax.x, vMax.y, vMax.z),
			Vector3(vMax.x, vMin.y, vMin.z),
			Vector3(vMax.x, vMax.y, vMin.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 0; }

		d = Ray_IntersectTriangle(ray,
			Vector3(vMax.x, vMax.y, vMax.z),
			Vector3(vMax.x, vMin.y, vMax.z),
			Vector3(vMax.x, vMin.y, vMin.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 0; }
	}

	// x-
	if (Vector3::Dot(ray.dir, Vector3(-1.0f, 0.0f, 0.0f))<0)
	{
		d = Ray_IntersectTriangle(ray,
			Vector3(vMin.x, vMax.y, vMax.z),
			Vector3(vMin.x, vMax.y, vMin.z),
			Vector3(vMin.x, vMin.y, vMin.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 1; }

		d = Ray_IntersectTriangle(ray,
			Vector3(vMin.x, vMax.y, vMax.z),
			Vector3(vMin.x, vMin.y, vMin.z),
			Vector3(vMin.x, vMin.y, vMax.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 1; }
	}

	// z+
	if (Vector3::Dot(ray.dir, Vector3(0.0f, 0.0f, 1.0f))<0)
	{
		d = Ray_IntersectTriangle(ray,
			Vector3(vMax.x, vMax.y, vMax.z),
			Vector3(vMin.x, vMin.y, vMax.z),
			Vector3(vMax.x, vMin.y, vMax.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 4; }

		d = Ray_IntersectTriangle(ray,
			Vector3(vMax.x, vMax.y, vMax.z),
			Vector3(vMin.x, vMax.y, vMax.z),
			Vector3(vMin.x, vMin.y, vMax.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 4; }
	}

	// z-
	if (Vector3::Dot(ray.dir, Vector3(0.0f, 0.0f, -1.0f))<0)
	{
		d = Ray_IntersectTriangle(ray,
			Vector3(vMax.x, vMax.y, vMin.z),
			Vector3(vMax.x, vMin.y, vMin.z),
			Vector3(vMin.x, vMin.y, vMin.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 5; }

		d = Ray_IntersectTriangle(ray,
			Vector3(vMax.x, vMax.y, vMin.z),
			Vector3(vMin.x, vMin.y, vMin.z),
			Vector3(vMin.x, vMax.y, vMin.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 5; }
	}


	// y+
	if (Vector3::Dot(ray.dir, Vector3(0.0f, 1.0f, 0.0f))<0)
	{
		d = Ray_IntersectTriangle(ray,
			Vector3(vMax.x, vMax.y, vMax.z),
			Vector3(vMax.x, vMax.y, vMin.z),
			Vector3(vMin.x, vMax.y, vMax.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 2; }

		d = Ray_IntersectTriangle(ray,
			Vector3(vMax.x, vMax.y, vMin.z),
			Vector3(vMin.x, vMax.y, vMin.z),
			Vector3(vMin.x, vMax.y, vMax.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 2; }
	}

	// y-
	if (Vector3::Dot(ray.dir, Vector3(0.0f, -1.0f, 0.0f))<0)
	{
		d = Ray_IntersectTriangle(ray,
			Vector3(vMax.x, vMin.y, vMax.z),
			Vector3(vMin.x, vMin.y, vMax.z),
			Vector3(vMax.x, vMin.y, vMin.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 3; }

		d = Ray_IntersectTriangle(ray,
			Vector3(vMax.x, vMin.y, vMin.z),
			Vector3(vMin.x, vMin.y, vMax.z),
			Vector3(vMin.x, vMin.y, vMin.z),
			true, &point);

		if (d >= 0.f && d < d_min) { d_min = d; point_nearest = point; hit = true; hit_face = 3; }
	}

	if (hit)
	{
		if (vHit) *vHit = point_nearest;
		if (faceId && hit_face != -1) *faceId = hit_face;
		return d_min;
	}

	return -1.f;
}
